package com.yanghui.dtracing.core;

import com.yanghui.dtracing.core.log.ILog;
import com.yanghui.dtracing.core.log.LogManager;
import com.yanghui.dtracing.core.match.ClassMatch;
import com.yanghui.dtracing.core.match.IndirectMatch;
import com.yanghui.dtracing.core.match.NameMatch;
import com.yanghui.dtracing.core.plugin.IClassEnhancePluginDefine;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.not;

public class PluginSearch {
	
	private static final ILog log = LogManager.getLogger(PluginSearch.class);

	private Map<String, List<IClassEnhancePluginDefine>> nameMatchDefines = new HashMap<>();

	private List<IClassEnhancePluginDefine> indirectMatchDefineList = new ArrayList<>();

    public PluginSearch(List<IClassEnhancePluginDefine> pluginDefineList){
        for(IClassEnhancePluginDefine define : pluginDefineList){
            ClassMatch classMatch = define.enhanceClass();
            if(null == classMatch){
                continue;
            }
            if(classMatch instanceof NameMatch){
                NameMatch nameMatch = (NameMatch)classMatch;
                List<IClassEnhancePluginDefine> defineList = this.nameMatchDefines.get(nameMatch.getClassName());
                if(null == defineList){
                    defineList = new ArrayList<>();
                    this.nameMatchDefines.put(nameMatch.getClassName(),defineList);
                }
                defineList.add(define);
            }else {
                this.indirectMatchDefineList.add(define);
            }
        }
    }

    public ElementMatcher<? super TypeDescription> buildMatch(){
        /** 类名匹配 **/
        ElementMatcher.Junction judge = new ElementMatcher.Junction<NamedElement>() {
            @Override
            public <U extends NamedElement> Junction<U> and(ElementMatcher<? super U> other) {
                return new Conjunction<U>(this, other);
            }
            @Override
            public <U extends NamedElement> Junction<U> or(ElementMatcher<? super U> other) {
                return new Disjunction<U>(this, other);
            }
            @Override
            public boolean matches(NamedElement target) {
                return nameMatchDefines.containsKey(target.getActualName());
            }
        };
        /** 忽略接口 **/
        judge = judge.and(not(isInterface()));
        /** 间接匹配 **/
        for(IClassEnhancePluginDefine define : this.indirectMatchDefineList){
            ClassMatch match = define.enhanceClass();
            if (match instanceof IndirectMatch) {
                judge = judge.or(((IndirectMatch) match).buildJunction());
            }
        }
        return new ProtectiveShieldMatcher(judge);
    }

    public boolean isHashPlugin(){
        if(this.nameMatchDefines.isEmpty() && this.indirectMatchDefineList.isEmpty()){
            return false;
        }
        return true;
    }

    public void printPlugin(){
        for(IClassEnhancePluginDefine pluginDefine : indirectMatchDefineList){
        	log.info("Load the plugin:{}",pluginDefine.getClass().getName());
        }
        for(Map.Entry<String,List<IClassEnhancePluginDefine>> entry : this.nameMatchDefines.entrySet()){
            if(entry.getValue() == null){
                continue;
            }
            for(IClassEnhancePluginDefine define : entry.getValue()){
                log.info("Load the plugin:{}",define.getClass().getName());
            }
        }
    }

    public List<IClassEnhancePluginDefine> find(TypeDescription typeDescription){
        List<IClassEnhancePluginDefine> resultList = new ArrayList<>();
        for(IClassEnhancePluginDefine define : this.indirectMatchDefineList){
            IndirectMatch indirectMatch = (IndirectMatch)define.enhanceClass();
            if(indirectMatch.isMatch(typeDescription)){
                resultList.add(define);
            }
        }
        String typeName = typeDescription.getTypeName();
        if(this.nameMatchDefines.containsKey(typeName)){
            resultList.addAll(this.nameMatchDefines.get(typeName));
        }
        return resultList;
    }
}
